// most of this file is from/based on 'tdl'

var $builtinmodule = function (name) {
    var mod = {};

    var Buffer = function (array, opt_target) {
        var target = opt_target || gl.ARRAY_BUFFER;
        var buf = gl.createBuffer();
        this.target = target;
        this.buf = buf;
        this.set(array);
        this.numComponents_ = array.numComponents;
        this.numElements_ = array.numElements;
        this.totalComponents_ = this.numComponents_ * this.numElements_;
        if (array.buffer instanceof Float32Array) {
            this.type_ = gl.FLOAT;
        } else if (array.buffer instanceof Uint8Array) {
            this.type_ = gl.UNSIGNED_BYTE;
        } else if (array.buffer instanceof Int8Array) {
            this.type_ = gl._BYTE;
        } else if (array.buffer instanceof Uint16Array) {
            this.type_ = gl.UNSIGNED_SHORT;
        } else if (array.buffer instanceof Int16Array) {
            this.type_ = gl.SHORT;
        } else {
            throw("unhandled type:" + (typeof array.buffer));
        }
    };

    Buffer.prototype.set = function (array) {
        gl.bindBuffer(this.target, this.buf);
        gl.bufferData(this.target, array.buffer, gl.STATIC_DRAW);
    };

    Buffer.prototype.type = function () {
        return this.type_;
    };

    Buffer.prototype.numComponents = function () {
        return this.numComponents_;
    };

    Buffer.prototype.numElements = function () {
        return this.numElements_;
    };

    Buffer.prototype.totalComponents = function () {
        return this.totalComponents_;
    };

    Buffer.prototype.buffer = function () {
        return this.buf;
    };

    Buffer.prototype.stride = function () {
        return 0;
    };

    Buffer.prototype.offset = function () {
        return 0;
    };


    mod.Model = Sk.misceval.buildClass(mod, function ($gbl, $loc) {
            $loc.__init__ = new Sk.builtin.func(function (self, shader, arrays, textures) {
                self.buffers = {};
                var setBuffer = function (name, array) {
                    var target = (name == "indices") ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER;
                    b = self.buffers[name];
                    if (!b) {
                        b = new Buffer(array, target);
                    } else {
                        b.set(array);
                    }
                    self.buffers[name] = b;
                };
                for (name in arrays) {
                    setBuffer(name, arrays[name]);
                }

                var textureUnits = {};
                var unit = 0;
                for (var texture in textures) {
                    textureUnits[texture] = unit++;
                }

                self.mode = gl.TRIANGLES;
                self.textures = textures.v;
                self.textureUnits = textureUnits;
                self.shader = shader;
            });

            /**
             * Sets up the shared parts of drawing this model. Uses the
             * program, binds the buffers, sets the textures.
             *
             * @param {!Object.<string, *>} uniforms An object of names to
             *     values to set on this models uniforms.
             */
            $loc.drawPrep = new Sk.builtin.func(function (self, uniforms) {
                var shader = self.shader;
                var buffers = self.buffers;
                var textures = self.textures;

                uniforms = Sk.ffi.remapToJs(uniforms);

                Sk.misceval.callsimArray(shader.use, [shader]);

                for (var buffer in buffers) {
                    var b = buffers[buffer];
                    if (buffer == "indices") {
                        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, b.buffer());
                    } else {
                        var attrib = shader.attrib[buffer];
                        if (attrib) {
                            attrib(b);
                        }
                    }
                }

                for (var texture in textures) {
                    var unit = self.textureUnits[texture];
                    shader.setUniform$impl(shader, textuer, unit);
                    textures[texture].bindToUnit(unit);
                }

                for (var uniform in uniforms) {
                    shader.setUniform$impl(shader, uniform, uniforms[uniform]);
                }
            });

            /**
             * Draws this model.
             *
             * After calling tdl.models.Model.drawPrep you can call this
             * function multiple times to draw this model.
             *
             * @param {!Object.<string, *>} uniforms An object of names to
             *     values to set on this models uniforms.
             */
            $loc.draw = new Sk.builtin.func(function (self, uniforms, opt_textures) {
                var shader = self.shader;
                uniforms = Sk.ffi.remapToJs(uniforms);
                for (uniform in uniforms) {
                    shader.setUniform$impl(shader, uniform, uniforms[uniform]);
                }

                if (opt_textures) {
                    for (var texture in opt_textures) {
                        var unit = self.textureUnits[texture];
                        shader.setUniform$impl(shader, texture, unit);
                        opt_textures[texture].bindToUnit(unit);
                    }
                }

                var buffers = self.buffers;
                gl.drawElements(self.mode, buffers.indices.totalComponents(), gl.UNSIGNED_SHORT, 0);
            });
        },
        'Model', []);

    return mod;
};
